# Copyright (C) 2007 Insecure.Com LLC.
#
# Authors: Guilherme Polo <ggpolo@gmail.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 
# USA

import gtk
import gobject
from math import sqrt
from gettext import gettext as _

from Calendar import monthname
from Base import check_instance

class TL_YouAreHere(gtk.Widget):
    """
    A localizator for TimeLine.
    """
    
    def __init__(self, tldaddy, show_border=True, draw_zoom=True):
        self.tl = check_instance(tldaddy) # timeline instance
        
        gtk.Widget.__init__(self)
        
        self.__focus = False
        
        self.draw_zoom = draw_zoom
        self.show_border = show_border
        self.hovering = - 1
        self.hovering_yah = False # yah = you are here
        self.point_yah = (-1, -1) # point where "You are here" arrow is
        
        self.tl.connect('date-change-notify', self.tlchange)
        self.tl.connect('range-change-notify', self.tlchange)
    

    def tlchange(self, event):
        """
        Some change (zoom or date) ocurried in self.daddy.
        """
        self.queue_draw()
    
    
    def get_draw_zoom(self):
        """
        Get option set for draw_zoom.
        """
        return self.__draw_z
    
    
    def set_draw_zoom(self, draw):
        """
        Set to True if a zoom in (+) and a zoom out (-) should be draw
        on top right corner.
        """
        self.__draw_z = draw
    

    def get_hovering(self):
        """
        Return hover state
        """
        return self.__hovering
    
    
    def get_hovering_yah(self):
        """
        Return True if "You are here" arrow is being hovered, otherwise,
        False.
        """
        return self.__yah_hover
        
        
    def get_point_yah(self):
        """
        Get the point where "You are here" arrow is.
        """
        return self.__yah_point

    
    def get_yah_inc(self):
        """
        Get the distance till next point
        """
        return self.__yah_inc
    
    
    def do_realize(self):
        self.set_flags(self.flags() | gtk.REALIZED | gtk.CAN_FOCUS)
        
        # create a new gtk.gdk.Window so we can draw on it ;)
        # also, lets make it possible to receive exposure events by 
        # setting the event_mask
        self.window = gtk.gdk.Window(self.get_parent_window(), 
                                     width=self.allocation.width,
                                     height=self.allocation.height, 
                                     window_type=gtk.gdk.WINDOW_CHILD,
                                     wclass=gtk.gdk.INPUT_OUTPUT,
                                     event_mask=self.get_events() | 
                                                gtk.gdk.EXPOSURE_MASK | 
                                                gtk.gdk.BUTTON_PRESS_MASK | 
                                                gtk.gdk.BUTTON_RELEASE_MASK |
                                                gtk.gdk.POINTER_MOTION_MASK |
                                                gtk.gdk.ENTER_NOTIFY_MASK | 
                                                gtk.gdk.LEAVE_NOTIFY_MASK)
        
        # associate the gtk.gdk.Window with ourselves, Gtk+ needs a reference
        # between the widget and the gdk window
        self.window.set_user_data(self)
        
        # attach the style to the gdk.Window, a style contains colors and
        # graphic contexts used for drawing                      
        self.style.attach(self.window)
        
        # the default color of the background should be what
        # theme engine tells us.
        self.style.set_background(self.window, gtk.STATE_NORMAL)
        
        self.window.move_resize(*self.allocation)


    def do_unrealize(self):
        """
        Release resources when requested.
        """
        self.window.set_user_data(None)


    def do_size_request(self, requisition):
        """
        This will be called when 'size-request' is emitted
        """
        # ToDo: calculate this based on font size
        requisition.width = 400
        requisition.height = 12*2 + 32 + 8


    def do_size_allocate(self, allocation):
        """
        If we are realized, move and resize the window to the requested
        coords/positions.
        """
        self.allocation = allocation
        
        if self.flags() & gtk.REALIZED:
            self.window.move_resize(*allocation)
    
    
    def do_expose_event(self, event):
        cr = self.window.cairo_create()
        cr.rectangle(*event.area)
        cr.clip()
        
        if self.tl.draw_border and self.__focus:
            self.__draw_border_box(cr) # draw a dotted box around widget
        
        if self.draw_zoom:
            self.__draw_zoom(cr)
        
        self.__draw_yah(cr) # draw an "You are here"
    
    
    def do_button_press_event(self, event):
        """
        Mouse button pressed
        """
        if event.type == 4 and self.hovering == 1: # clicked on "You are here"
                                                   # arrow.
            self.hovering_yah = True
            self.point_yah = ((event.x, event.y))
                
        self.grab_focus()


    def do_button_release_event(self, event):
        """
        Mouse button released
        """
        win_origin = self.window.get_origin()
        
        if self.hovering == 1: # was dragging "You are here" arrow
            self.hovering_yah = False
            self.point_yah = ((-1, -1))
            self.hovering = -1
            self.queue_draw()
        elif self.hovering == 2 and self.tl.can_zoom_in(): # zoom in (+)
            self.tl.zoom_in()
        elif self.hovering == 3 and self.tl.can_zoom_out(): # zoom out (-)
            self.tl.zoom_out()
    
    
    def do_leave_notify_event(self, event):
        """
        Mouse left the widget
        """
        self.queue_draw()
    
    
    def do_motion_notify_event(self, event):  
        """
        Mouse motion over the widget
        """    
        prev_state = self.hovering
        prev_ami_point = self.point_yah
        
        if prev_ami_point != (-1, -1) and \
            prev_ami_point[1] - 10 <= event.y <= prev_ami_point[1] + 10 and\
            prev_ami_point[0] - 10 <= event.x <= prev_ami_point[0] + 10:
            self.hovering = 1 # hovering "You are here" arrow
        
        elif self.hovering_yah: # moving "You are here" arrow
            if event.x - self.point_yah[0] >= self.yah_increment: # next date
                self.tl.go_next_date()
                
                self.point_yah = ((event.x, prev_ami_point[1]))
                self.tl.emit('date-change-notify')
                
            elif self.point_yah[0] - event.x >= self.yah_increment: # prev
                self.tl.go_prev_date()
                
                self.point_yah = ((event.x, prev_ami_point[1]))
                self.tl.emit('date-change-notify')
        
        elif 10 <= event.x <= 20 and \
            self.allocation[3] - 15 <= event.y <= self.allocation[3] - 5:
            self.hovering = 2 # hovering zoom in (+)
        
        elif 25 <= event.x <= 35 and \
            self.allocation[3] - 15 <= event.y <= self.allocation[3] - 5: 
            self.hovering = 3 # hovering zoom out (-)
        
        else:
            self.hovering = -1
        
        if prev_state != self.hovering:
            """
            State changed, redraw.
            """
            self.queue_draw()
    

    def do_focus_in_event(self, event):
        """
        Keyboard entered the widget
        """
        self.__focus = True

    
    def do_focus_out_event(self, event):
        """
        Keyboard left the widget
        """
        self.__focus = False
        self.hovering = -1
        self.queue_draw()
    
    
    ##
    # Private methods follows.
    
    def __set_hovering(self, hover):
        """
        States: -1 -> Nothing being hovered
                 1 -> Hovering "You are here" arrow
        """
        self.__hovering = hover
        if hover != 1:
            self.hovering_yah = False
    
    
    def __set_hovering_yah(self, hover):
        """
        Set to True if hovering "You are here" arrow, otherwise, set to False.
        """
        self.__yah_hover = hover
    
    
    def __set_point_yah(self, point):
        """
        Set a point where "You are here" arrow is.
        """
        self.__yah_point = point
    
    
    def __set_yah_inc(self, inc):
        """
        Distance till next date.
        """
        self.__yah_inc = inc
    

    def __draw_border_box(self, cr):
        """
        Draws a rectangle box, shown when widget is focused.
        """
        cr.save()
        cr.set_line_width(0.2)
        
        cr.set_dash([1, 1], 0)
        cr.new_sub_path()
        cr.rectangle(0, 0, self.allocation[2], self.allocation[3])
        
        cr.close_path()
        cr.stroke()
        cr.restore() 

    
    def __draw_zoom(self, cr):
        """
        Draw + and - for zoom in and zoom out at left bottom corner.
        """
        x_ini = 10
        y_ini = self.allocation[3] - 10
        
        cr.save()
        
        # Draw +
        cr.move_to(x_ini, y_ini)
        cr.rel_line_to(10, 0)
        cr.move_to(x_ini + 5, y_ini - 5)
        cr.rel_line_to(0, 10)
        
        if self.hovering == 2 and self.tl.can_zoom_in():
            cr.set_source_rgb(1, 0, 0)
        elif not self.tl.can_zoom_in():
            cr.set_source_rgba(0, 0, 0, 0.2)
        else:
            cr.set_source_rgba(0, 0, 0, 0.5)
        cr.stroke()
        
        # Draw -
        cr.move_to(x_ini + 15, y_ini)
        cr.rel_line_to(10, 0)
        
        if self.hovering == 3 and self.tl.can_zoom_out():
            cr.set_source_rgb(1, 0, 0)
        elif not self.tl.can_zoom_out():
            cr.set_source_rgba(0, 0, 0, 0.2)
        else:
            cr.set_source_rgba(0, 0, 0, 0.5)
        cr.stroke()
        
        cr.restore()


    def __draw_yah(self, cr):
        """
        Draws the "You are here".
        """
        
        x_wini = 16
        y_wini = self.allocation[3] - 28
        x_wend = self.allocation[2]
        
        current_zoom = self.tl.zoom
        
        # Calculate positions first
        
        ## ticks and dates (min, max -> years, months, days or hours)
        if current_zoom == 0:
            range_init = self.tl.year_range[0]
            range_end = self.tl.year_range[1]
            init = str(range_init)
            end = str(range_end)
            legend = _("Years")            
            self.yah_increment = float(x_wend - 2 * x_wini) / \
                            (range_end - range_init)
            where = self.yah_increment * (self.tl.year - range_init)
        
        elif current_zoom == 1:
            init = monthname(1)[:3]
            end = monthname(12)[:3]
            legend = _("Months")
            
            self.yah_increment = float(x_wend - 2 * x_wini) / 11
            where = self.yah_increment * (self.tl.month - 1)
        
        elif current_zoom == 2:
            init = "1"
            range_end = self.tl.get_current_monthrange()[1]
            end = str(range_end)
            legend = _("Days")
            self.yah_increment = float(x_wend - 2 * x_wini) / (range_end - 1)
            where = self.yah_increment * (self.tl.day - 1)
        
        elif current_zoom == 3:
            init = "0"
            end = "23"
            legend = _("Hours")
            self.yah_increment = float(x_wend - 2 * x_wini) / 23
            where = self.yah_increment * self.tl.hour
        
        prev_point = self.point_yah
        if prev_point != (where+x_wini, y_wini-17):
            self.point_yah = ((where+x_wini, y_wini-17))
        
        date_title = self.tl.title_by_zoom()
        
        cr.save()
        
        # Draw |_______|
        cr.move_to(x_wini, y_wini)
        cr.line_to(x_wini, y_wini + 8)
        cr.line_to(x_wend-x_wini, y_wini + 8)
        cr.line_to(x_wend-x_wini, y_wini)
        cr.stroke()
        
        # Write init, end and legend texts
        cr.set_font_size(12)
        
        cr.move_to(x_wini-4, y_wini)
        cr.rotate(-120)
        cr.show_text(init)
        cr.rotate(120)
        
        cr.move_to(x_wend-28, y_wini)
        cr.rotate(-120)
        cr.show_text(end)
        cr.rotate(120)
        
        n, n, width, n, n, n = cr.text_extents(legend)
        cr.move_to(x_wend-width-4, y_wini + 20)
        cr.show_text(legend)
      
        # Draw a larger tick and a small triangle
        cr.move_to(where+x_wini, y_wini+8)
        cr.line_to(where+x_wini, y_wini-14)
        cr.line_to(where+x_wini+4, y_wini-17)
        cr.line_to(where+x_wini-4, y_wini-17)
        cr.line_to(where+x_wini, y_wini-14)
        if self.hovering == 1:
            cr.set_source_rgb(1, 0, 0)
        else:
            cr.set_source_rgba(0, 0, 0, 0.5)
        cr.stroke()
        
        # Write "Yah!" above tick
        t2 = _("Yah!")
        (tx, ty, tw, th, dtx, dty) = cr.text_extents(t2)
        cr.move_to(where + x_wini - (tw / 2), y_wini - 20)
        cr.show_text(t2)
        
        # Show title for date
        cr.set_source_rgba(1, 0, 0, 0.5)
        (tx, ty, tw, th, dtx, dty) = cr.text_extents(date_title)
        cr.move_to(self.allocation[2]/2.0 - dtx/2, y_wini + 20)     
        cr.show_text("%s" % date_title)
        
        cr.restore()


    # Properties
    draw_zoom = property(get_draw_zoom, set_draw_zoom)
    hovering = property(get_hovering, __set_hovering)
    hovering_yah = property(get_hovering_yah, __set_hovering_yah)
    point_yah = property(get_point_yah, __set_point_yah)
    yah_increment = property(get_yah_inc, __set_yah_inc)
    
    
gobject.type_register(TL_YouAreHere)
